//============================================================================
// Copyright (C) 2013 Brett R. Jones
//
// Issued to public domain 2013
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================


#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <string.h>
#include "VxUtilLib.h"
#include "VxSktUtil.h"
#include "VxTimer.h"

#ifdef WIN_PLATFORM
    // helpers for adapter info
	#include <iphlpapi.h>
	#pragma comment(lib, "IPHLPAPI.lib")
#else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
	#include <net/if.h> 
    #include <netdb.h>
	#include <ctype.h>
	#include <fcntl.h>
    #include <sys/types.h>
#endif // _MSC_VER


std::vector<VxInetAddress>  g_aoAllIps;
VxInetAddress				g_oSelectedLocalIp;
VxInetAddress				g_oDefaultLocalIp;
VxInetAddress				g_oDefaultIPv4;
VxInetAddress				g_oDefaultIPv6;
VxInetAddress				g_oGlobalIPv6;

//---------------------------------------------------------------------------
//! initialize sockets
RCODE VxSocketsStartup( void )
{
	srand( (unsigned int)time( NULL ) );
	static BOOL bIsInitialized = 0;
	if( bIsInitialized )
	{
		return 0;
	}
#ifdef WIN_PLATFORM
	static struct WSAData wsa_state;
	memset(&wsa_state,0,sizeof(wsa_state));
	if (WSAStartup(0x0202,&wsa_state)!=0)
	{
		int err = WSAGetLastError();
		log_msg( 0, "VxSocketsStartup: Error %d\n", err );
		vx_assert( false );
		return(-1);
	}
#endif // WIN_PLATFORM
	VxRefreshDefaultIps();

	bIsInitialized = true;
	return  0;
}
//---------------------------------------------------------------------------
//=== ip address conversions ===//
//---------------------------------------------------------------------------
//! NOT THREAD SAFE ip address to string
char *		VxIpToString(	U32 u32IpAddr )
{
	// to try to avoid thread problems we do up to 8 different ip buffers
	static int iIdx = 0;
	static char as8Buf[32 * 8 ];
	unsigned char * pTemp = (unsigned char * )&u32IpAddr;
	char * pIp = &as8Buf[ iIdx * 32 ];
	iIdx++;
	if( iIdx > 7 )
	{
		iIdx = 0;
	}
	sprintf( pIp, "%i.%i.%i.%i", pTemp[3], pTemp[2], pTemp[1], pTemp[0] );
	return pIp;
}

//============================================================================
void VxFillHints( struct addrinfo& oHints, bool bUdpSkt)
{
	memset(&oHints, 0, sizeof(oHints));
	oHints.ai_flags = AI_PASSIVE; // AI_NUMERICHOST | AI_PASSIVE
	oHints.ai_socktype = bUdpSkt ? SOCK_DGRAM : SOCK_STREAM;
	oHints.ai_protocol = bUdpSkt ? IPPROTO_UDP : IPPROTO_TCP;
	oHints.ai_family = AF_INET; /* AF_INET, AF_INET6 or AF_UNSPEC */
}

//============================================================================
void VxIP_ntop( int family, void * pvBinary, char * pBuf, int iBufLen, bool isHostOrder )
{
	if( AF_INET == family )
	{
		VxIPv4_ntop( pvBinary, pBuf, iBufLen, isHostOrder );
	}
	else
	{
		VxIPv6_ntop( pvBinary, pBuf, iBufLen );
	}
}

//============================================================================
void VxIPv4_ntop( void * pvBinary, char * pBuf, int iBufLen, bool isHostOrder )
{
    U8 * pTemp = 0;
	U32 u32NetOrder;
	if( isHostOrder )
	{
		// change to net order
		u32NetOrder = htonl( (U32)pvBinary );
		pTemp = (U8 *)&u32NetOrder;
	}
	else
	{
		pTemp = (U8 *)pvBinary;
	}
	sprintf( pBuf, "%i.%i.%i.%i", pTemp[3], pTemp[2], pTemp[1], pTemp[0] );
}

//============================================================================
void VxIPv6_ntop( void * pvBinary, char * pBuf, int iBufLen )
{
	U16 * pVal = (U16 *)pvBinary;

	bool isValid = false;
	for( int i = 0; i <8; ++i )
	{
		if( 0 != pVal[i] )
		{
			isValid = true;
			break;
		}
	}

	if( false == isValid )
	{
		strcat( pBuf, "::" );
	}
	else
	{
		int colonCount = 0;
		for( int i = 0; i < 8; i++)
		{
			if( 0 == pVal[i])
			{
				if( 0 == colonCount )
				{
					// beginning of address
					strcat( pBuf, "::");
					colonCount = 2;
				}
				else if( 1 == colonCount )
				{
					strcat( pBuf, ":");
					colonCount++;
				}
			}
			else
			{
				sprintf(&pBuf[strlen(pBuf)], "%4.4x", htons(pVal[i]));
				colonCount = 0;
				if( 7 != i )
				{
					strcat( pBuf, ":" );
					colonCount = 1;
				}
			}
		}
	}
}

//============================================================================
void VxIPv4_pton( const char * pIpStr, void * pvBinary, bool wantHostOrder )
{
	U32 u32Ip = 0;
	//U32 u32Octet;
	if( NULL == pIpStr )
	{
		vx_assert( pIpStr );
		return;
	}
	int iStrLen = (int)strlen( pIpStr );
	if( ( iStrLen >=  7 ) &&
		( iStrLen <  16 ) && 
		isdigit( pIpStr[0] ) )
	{
		/*
		char as8Buf[ 30 ];
		strcpy( as8Buf, pIpStr );
		int iDotCnt = 0;
		char * pTemp = as8Buf;
		int iShift = 24;
		for( int i = 0; i < 3; i++ )
		{
			pTemp = strtok( (char *)pTemp, "." );
			vx_assert( pTemp );
			iDotCnt++;
			u32Octet = atoi( pTemp );
			vx_assert( u32Octet < 256 );
			u32Ip |= u32Octet << iShift ;
			iShift -= 8;
			pTemp = pTemp + strlen( pTemp )+1;
		}
		vx_assert( iDotCnt == 3 );
		u32Octet = atoi( pTemp );
		vx_assert( u32Octet < 256 );
		u32Ip |= u32Octet; 
		*/
		u32Ip = inet_addr(pIpStr);	
	}
	else
	{
		vx_assert( iStrLen >=  7 );
		vx_assert( iStrLen <  16 );
		vx_assert( isdigit( pIpStr[0] ) );
		u32Ip = 0;
	}
	if( wantHostOrder )
	{
		*((U32 *)pvBinary) = u32Ip;
	}
	else
	{
		*((U32 *)pvBinary) = ntohl(u32Ip);
	}
}
//============================================================================
void VxIPv6_pton( const char * pIpStringIn,  void * pvBinary )
{
	U16 * pBinary = (U16 *)pvBinary; 
	std::vector<U16> aoValues;
	int iStrLen = strlen( pIpStringIn );
	char * pIpString = new char[iStrLen + 1];
	strcpy( pIpString, pIpStringIn );

	while( 0 != pIpString[0] )
	{
		if( ':' == pIpString[0] )
		{
			aoValues.push_back(0);
			pIpString += 1;
		}
		else 
		{
			U16 u16Value;
			if( strchr( pIpString, ':') )
			{
				char * pValue = strtok( (char *)pIpString, ":");
				if( pValue )
				{
					u16Value = (U16)strtol( pIpString, NULL, 16 );
					aoValues.push_back( htons( u16Value ) );
				}
				pIpString += strlen( pValue ) + 1;
				
			}
			else
			{
				u16Value = (U16)strtol( pIpString, NULL, 16 );
				aoValues.push_back( htons( u16Value ) );
				break;
			}
		}
	}
	int iSize =  (int)aoValues.size();
	if( 8 != iSize )
	{
		int iZerosToAdd = 8 - iSize;
		U16 u16Zero = 0;
		std::vector<U16>::iterator oIter;
		for( oIter = aoValues.begin(); oIter != aoValues.end(); ++oIter )
		{
			if( 0 == *oIter )
			{
				aoValues.insert( oIter, iZerosToAdd, u16Zero );
				break;
			}
		}
	}
	if( 8 == aoValues.size() )
	{
		for( int i = 0; i < 8; i++ )
		{
			pBinary[i] = aoValues[i];
		}
	}
	else
	{
		log_msg( LOG_ERROR, "Invalid IP %s\n", pIpStringIn );
	}
}

//---------------------------------------------------------------------------
//! if new connection refresh default ip(s)
void VxRefreshDefaultIps( void )
{
	g_aoAllIps.clear();
	g_oDefaultLocalIp.setToInvalid();
	g_oDefaultIPv4.setToInvalid();
	g_oDefaultIPv6.setToInvalid();
	g_oGlobalIPv6.setToInvalid();

    g_oDefaultLocalIp.getAllAddresses(g_aoAllIps);
	if( g_aoAllIps.size() )
	{
		g_oDefaultLocalIp = g_aoAllIps[0];
		if( false == g_oSelectedLocalIp.isValid() )
		{
			g_oSelectedLocalIp = g_oDefaultLocalIp;
		}

		std::vector<VxInetAddress>::iterator oIter;
		for( oIter = g_aoAllIps.begin(); oIter != g_aoAllIps.end(); ++oIter )
		{
			if( (false == g_oDefaultIPv4.isValid()) && (*oIter).isIPv4() )
			{
				g_oDefaultIPv4 = *oIter;
			}
			if( (false == g_oDefaultIPv6.isValid()) && (*oIter).isIPv6() )
			{
				g_oDefaultIPv6 = *oIter;
			}			
			if( (false == g_oGlobalIPv6.isValid()) && (*oIter).isIPv6GlobalAddress() )
			{
				g_oGlobalIPv6 = *oIter;
			}
		}
	}
}

//---------------------------------------------------------------------------
bool VxCanConnectUsingIPv6( void )
{
	return ( g_oDefaultIPv6.isValid() || g_oGlobalIPv6.isValid() );
}

//---------------------------------------------------------------------------
//! get list of local ips.. needed for systems with multiple tcp hardware
std::vector<VxInetAddress>& VxGetLocalIps( void )	
{
	if( 0 == g_aoAllIps.size() )
	{
		VxRefreshDefaultIps();
	}
	return 	g_aoAllIps;
}

//---------------------------------------------------------------------------
void VxGetLocalIps(	std::vector<VxInetAddress>& aRetIpAddress )
{
    if( 0 == g_aoAllIps.size() )
    {
        VxRefreshDefaultIps();
    }
	aRetIpAddress.clear();
	std::vector<VxInetAddress>::iterator oIter;
	for( oIter = g_aoAllIps.begin(); oIter != g_aoAllIps.end(); ++oIter )
	{
		aRetIpAddress.push_back( *oIter );
	}
}

//---------------------------------------------------------------------------
//! get default local ip address.. returns 0 if none
VxInetAddress VxGetDefaultLocalIp( void )
{
	return g_oDefaultLocalIp;
}

//---------------------------------------------------------------------------
//! get default local ip address.. returns 0 if none
VxInetAddress VxGetSelectedLocalIp( void )
{
	return g_oSelectedLocalIp;
}

//---------------------------------------------------------------------------
VxInetAddress	VxGetMyGlobalIPv6Address( void )
{
	return g_oGlobalIPv6;
}

//---------------------------------------------------------------------------
VxInetAddress	VxGetDefaultIPv4Address( void )
{
	return g_oDefaultIPv4;
}

//---------------------------------------------------------------------------
VxInetAddress	VxGetDefaultIPv6Address( void )
{
	return g_oDefaultIPv6;
}

//---------------------------------------------------------------------------
//! return true if ip is in list of local ips
bool VxLocalIpExists(  std::string& strIpAddress )
{
	std::vector<VxInetAddress>::iterator oIter;
	for( oIter = g_aoAllIps.begin(); oIter != g_aoAllIps.end(); ++oIter )
	{
		if( strIpAddress == (*oIter).toStdString() )
		{
			return true;
		}
	}
	return false;
}

//---------------------------------------------------------------------------
//! return true if port is already in use on this local ip address
bool VxIsIpPortInUse(  U16 u16Port, const char * pLocalIp )
{
	bool bInUse = true;

	VxInetAddress addr;
	if( pLocalIp )
	{
		addr.setIp( pLocalIp );
	}

	SOCKET listenSocket = socket(addr.isIPv4() ? AF_INET : AF_INET6, SOCK_STREAM, 0);
	if( INVALID_SOCKET == listenSocket )
	{
		//socket create error
		log_msg( LOG_ERROR, "VxIsIpPortInUse: socket create error %s\n", DescribeSktErr( VxGetLastError() ) );
		return	bInUse;
	}


	if( false == VxBindSkt( listenSocket, addr, u16Port ) )
	{
		log_msg( LOG_INFO, "VxIsIpPortInUse: bind error %s\n", DescribeSktErr( VxGetLastError() ) );
	}
	else
	{
		RCODE rc;
		if( 0 != ( rc = listen( listenSocket, SOMAXCONN ) ) )
		{
			//listen error
			rc = VxGetLastError();
			log_msg( LOG_ERROR, "RcSktAcceptMgr::startListening: listen error %s\n", DescribeSktErr( rc ) );

		}
		else
		{
			bInUse = false;
		}

	}
    VxCloseSkt( listenSocket );
	return bInUse;
}

//---------------------------------------------------------------------------
//=== socket errors ===//
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
const char * DescribeSktErr( int iErr )
{
	static char as8Buf[128];
#ifdef WIN_PLATFORM
	switch( iErr )
	{
	case WSANOTINITIALISED:	return "A successful AfxSocketInit must occur before using this API.";
	case WSAENETDOWN:		return "The Windows Sockets implementation detected that the network subsystem failed.";
	case WSAEADDRINUSE:		return "The specified address is already in use.";
	case WSAEINPROGRESS:	return "A blocking Windows Sockets call is in progress.";
	case WSAEADDRNOTAVAIL:	return "The specified address is not available from the local machine.";
	case WSAEAFNOSUPPORT:	return "Addresses in the specified family cannot be used with this socket.";
	case WSAECONNREFUSED:	return "The attempt to connect was rejected.";
//	case WSAEDESTADDREQ:	return "A destination address is required.";
	case WSAEFAULT:			return "The nSockAddrLen argument is incorrect.";
	case WSAEINVAL:			return "The socket is not already bound to an address.";
	case WSAEISCONN:		return "The socket is already connected.";
	case WSAEMFILE:			return "No more file descriptors are available.";
	case WSAENETUNREACH:	return "The network cannot be reached from this host at this time.";
	case WSAENOBUFS:		return "No buffer space is available. The socket cannot be connected.";
	case WSAENOTSOCK:		return "The descriptor is not a socket.";
	case WSAETIMEDOUT:		return "Attempt to connect timed out without establishing a connection.";
	case WSAEWOULDBLOCK:	return "The socket is marked as nonblocking and the connection cannot be completed immediately.";
	case WSAEOPNOTSUPP:		return "MSG_OOB was specified, but the socket is not of type SOCK_STREAM.";
	case WSAESHUTDOWN:		return "The socket has been shut down; it is not possible to call Receive on a socket after ShutDown has been invoked with nHow set to 0 or 2.";
	case WSAEMSGSIZE:		return "The datagram was too large to fit into the specified buffer and was truncated.";
	case WSAECONNABORTED:	return "The virtual circuit was aborted due to timeout or other failure.";
	case WSAECONNRESET:		return "The virtual circuit was reset by the remote side.";
	case 10057:				return "send or receive data was disallowed because the socket is not connected";
    default:				return "Windows Unknown socket error";
	}
#else
	log_msg( LOG_ERROR, "DescribeSktErr Linux %d\n", iErr );
	switch( iErr )
	{
	case 1: // EPERM
		return "Operation not permitted.";
	case 2: // ENOENT           2      
		return "No such file or directory.";
	case 3: // ESRCH            3      
		return "No such process.";
	case 4: // EINTR            4      
		return "Interrupted system call.";
	case 5: // EIO              5     
		return "I/O error.";
	case 6: // ENXIO            6      
		return "No such device or address.";
	case 7: // E2BIG            7      
		return "Arg list too long.";
	case 8: // ENOEXEC          8      
		return "Exec format error.";
	case 9: // EBADF            9      
		return "Bad file number.";
	case 10: // ECHILD          10     
		return "No child processes.";
	case 11: // EAGAIN          11     
		return "Try again.";
	case 12: // ENOMEM          12     
		return "Out of memory.";
	default:
		sprintf( as8Buf, "Unknown Linux Socket Error %d", iErr );
		return as8Buf;
	}
#endif // WIN_PLATFORM
}

//============================================================================
//! split host name from website file path
bool VxSplitHostAndFile(	const char * pFullUrl,		// full url.. example http://www.mysite.com/index.html or www.mysite.com/images/me.jpg
							std::string& strRetHost,	// return host name.. example http://www.mysite.com/index.htm returns www.mysite.com
							std::string& strRetFileName,	// return file name.. images/me.jpg
							U16& u16RetPort )			// return port if specified else return 80 as default				
{
	char * pTemp;
	char as8Url[2048];

	u16RetPort = 80;
	strRetHost = "";
	strRetFileName = "";

	pTemp = (char *)strchr( pFullUrl, '/' );
	if( NULL == pTemp )
	{
		// assume is just ip address
		if( '[' == pFullUrl[0] )
		{
			strcpy( as8Url, &pFullUrl[1] );
			if( pTemp == strchr(  as8Url, ']' ))
			{
				*pTemp = 0;
				strRetHost = as8Url;
			}
		}
		else
		{
			strRetHost = pFullUrl;
		}
		return true;
	}

	//copy to buffer without protocol specification like https:
	pTemp = (char *)strstr( pFullUrl, "://" );
	if( pTemp )
	{
		strcpy( as8Url, pTemp + 3 ) ;
	}
	else
	{
		strcpy( as8Url, pFullUrl ) ;
	}
	if( 0 == strnicmp( pFullUrl, "https://", 8 ) )
	{
		u16RetPort = 443;
	}
	bool bHasCustomPort = false;
	if( '[' == as8Url[0] )
	{
		// its a ipv6 address
		pTemp = (char *)strchr( as8Url,']' );
		if( pTemp )
		{
			*pTemp = 0;
		}
		strRetHost = &as8Url[ 1 ];
		strcpy( &as8Url[0], pTemp + 1 );
		if( ':' == as8Url[0] )
		{
			bHasCustomPort = true;
		}
	}
	else
	{
		int iHostLen = (int)strlen( as8Url );
		for( int i = 0; i < iHostLen; i++ )
		{
			char c = as8Url[ i ];
			if( c == ':' ||
				c == '/' ||
				c == '\\' )
			{
				if( ':' == c )
				{
					bHasCustomPort = true;
				}
				as8Url[i] = 0;
				strRetHost = as8Url;
				as8Url[i] = c;
				break;
			}
		}
	}
	if( bHasCustomPort )
	{
		//see if has  custom Port 
		pTemp = strchr( as8Url, ':' );
		if ( pTemp !=NULL)
		{
			//had custom port so set it
			u16RetPort = (U16) atoi( pTemp + 1 );
		}
	}

	// get the url
	int iHostLen = (int)strlen( as8Url );
	for( int i = 0; i < iHostLen; i++ )
	{
		if( ('/' == as8Url[ i ] ) ||
			('\\' == as8Url[ i ] ) )
		{
			strRetFileName = &as8Url[ i + 1 ];
			break;
		}
	}
	return true;
}

//============================================================================
SOCKET VxConnectTo( VxInetAddress& oLclIp, VxInetAddress& oRmtIp, U16 u16Port, int iConnectTimeoutMs )
{
	fd_set			oSktSet; 
    socklen_t		iSktLen;
	struct timeval	tv;
	SOCKET			oSkt;
	int				iResult = 0;
	std::string		strRmtIp;
	oRmtIp.toStdString( strRmtIp );

	RCODE rc = 0;

	struct addrinfo * poAddrInfo;
	struct addrinfo * poResultAddr;
	struct addrinfo oHints;
	VxFillHints( oHints );

	char as8Port[16];
	sprintf( as8Port, "%d", u16Port);

	int error = getaddrinfo( NULL, as8Port, &oHints, &poAddrInfo );
	if (error != 0) 
	{
		return false;
	}
	bool bConnectSuccess = false;
	struct sockaddr_storage oSktAddr;
	oRmtIp.fillAddress( oSktAddr, u16Port);
	int iRmtSktAddrLen = sizeof( struct sockaddr_storage );

	/* Try all returned addresses until one works */
	for( poResultAddr = poAddrInfo; poResultAddr != NULL; poResultAddr = poResultAddr->ai_next ) 
	{
		//log_msg( LOG_SKT, "VxConnectTo: create socket for ip %s port %d\n", strRmtIp.c_str(), u16Port );

		oSkt = socket( poResultAddr->ai_family, poResultAddr->ai_socktype, poResultAddr->ai_protocol );
		if( INVALID_SOCKET == oSkt )
		{
			// create socket error
			rc = VxGetLastError();
			log_msg( 0, "VxConnectTo: socket create error %s\n", DescribeSktErr( rc ) );
			return oSkt;
		}
		//log_msg( LOG_SKT, "VxConnectTo: set socket option for ip %s port %d timeout %d\n", strRmtIp.c_str(), u16Port, iConnectTimeoutMs );
		// allow same port to be reused
		int iReusePort = true;
		if( SOCKET_ERROR == setsockopt( oSkt, SOL_SOCKET,SO_REUSEADDR, (const char *)&iReusePort, sizeof(iReusePort)) ) 
		{
            #ifdef WIN_PLATFORM
                // clear any old errors
                VxGetLastError();
            #endif // WIN_PLATFORM
		}

		/*
		if( SOCKET_ERROR == bind( oSkt, poResultAddr->ai_addr, poResultAddr->ai_addrlen ) )
		{
			// bind error
			rc = VxGetLastError();
			log_msg( 0, "VxConnectTo: open port %d error %s\n", 
				u16Port,
				DescribeSktErr( rc ) );
			VxCloseSktNow( oSkt );
			oSkt = INVALID_SOCKET;
			return oSkt;
		}
		*/

		// attempt to connect
		if( iConnectTimeoutMs )
		{
			//log_msg( LOG_SKT, "VxConnectTo: timeout %d skt handle %d connect no block ip %s port %d\n", iConnectTimeoutMs, oSkt, strRmtIp.c_str(), u16Port );

			// connect to the ip without timeout
			// set to non blocking
			VxSetSktBlocking( oSkt, false );
			//log_msg( LOG_INFO, "VxConnectTo: skt %d attempt no block connect\n", m_iSktId );

			iResult = connect( oSkt, (struct sockaddr *)&oSktAddr, iRmtSktAddrLen );
			if( 0 == iResult )
			{
				// connected
				VxSetSktBlocking( oSkt, true );
#ifdef DEBUG_SKTS
				log_msg( LOG_SKT, "VxConnectTo: SUCCESS skt handle %d connect no block ip %s port %d\n", oSkt, strRmtIp.c_str(), u16Port );
#endif // DEBUG_SKTS
				bConnectSuccess = true;
				goto connected;
			}

			rc = VxGetLastError();
			if( rc != EINPROGRESS )
			{
				// some other error other than need more time
#ifdef DEBUG_SKTS
				log_msg( LOG_SKT, "VxConnectTo: skt connect to %s %d error %s\n", strRmtIp.c_str(), u16Port, DescribeSktErr( rc ) );
#endif // DEBUG_SKTS
				VxCloseSktNow( oSkt );
				continue;
			}
			// needed more time so do connect with timeout
			VxSetSktBlocking( oSkt, true );
			do
			{ 
#ifdef DEBUG_SKTS
				log_msg( LOG_SKT, "VxConnectTo: skt handle %d Attempt connect with timeout %d\n", oSkt, iConnectTimeoutMs );
#endif // DEBUG_SKTS
				tv.tv_sec = iConnectTimeoutMs / 1000; 
				tv.tv_usec = (iConnectTimeoutMs % 1000) * 1000; 
				FD_ZERO(&oSktSet); 
				FD_SET(oSkt, &oSktSet); 
				iResult = (int)select(oSkt+1, NULL, &oSktSet, NULL, &tv); 
				//log_msg( LOG_INFO, "VxConnectTo: skt %d Attempt connect with timeout result %d\n", m_iSktId, iResult );
				if (iResult < 0 && 
					(errno != 4) &&
					(errno != 10004)  )

				{ 
					// error
#ifdef DEBUG_SKTS
					log_msg( LOG_SKT, "VxConnectTo: skt handle %d timeout %d select error %d\n", oSkt, iConnectTimeoutMs, errno );
#endif // DEBUG_SKTS
					VxCloseSktNow( oSkt );
					break;
				} 
				else if( iResult > 0 )
				{ 
					//log_msg( LOG_INFO, "VxConnectTo: skt %d Selecting for write\n", m_iSktId );
					// Socket selected for write 
					iSktLen = sizeof(int); 
                    socklen_t valopt = 0;
					if( getsockopt(oSkt, SOL_SOCKET, SO_ERROR, (char*)(&valopt), &iSktLen) < 0 )
					{ 
#ifdef DEBUG_SKTS
						log_msg( 0, "VxConnectTo: skt handle %d Error %d in getsockopt() to ip %s\n", oSkt, VxGetLastError(), strRmtIp.c_str() );
#endif // DEBUG_SKTS
						VxCloseSktNow( oSkt );
						break;
					} 
					else
					{
						//log_msg( LOG_INFO, "VxConnectTo: skt %d Selecting for write value returned %d\n", m_iSktId, valopt );
						// Check the value returned... 
						if( valopt ) 
						{ 
#ifdef DEBUG_SKTS
							log_msg( LOG_INFO, "VxConnectTo: skt handle %d Error %d in delayed connection to ip %s\n", oSkt, valopt, strRmtIp.c_str() );
#endif // DEBUG_SKTS
							VxCloseSktNow( oSkt );
							break;
						}
						else
						{
							// connected
#ifdef DEBUG_SKTS
							log_msg( LOG_SKT, "VxConnectTo: SUCCESS skt handle %d connected in delayed connection to ip %s\n", oSkt, strRmtIp.c_str() );
#endif // DEBUG_SKTS
							VxSetSktBlocking( oSkt, true );
							bConnectSuccess = true;
							break;
						} 
					}
				} 
				else 
				{ 
#ifdef DEBUG_SKTS
					log_msg( LOG_SKT, "VxConnectTo: skt handle %d connect to %s port %d timed out.. canceling\n", oSkt, strRmtIp.c_str(), u16Port );
#endif // DEBUG_SKTS
					VxCloseSktNow( oSkt );
					break; 
				} 
			} while (1); 
			if( bConnectSuccess )
			{
				break;
			}
		}
		else
		{
#ifdef DEBUG_SKTS
			log_msg( LOG_SKT, "VxConnectTo: NO TIMEOUT skt handle %d connect no block ip %s port %d\n", oSkt, strRmtIp.c_str(), u16Port );
#endif // DEBUG_SKTS
			// connect to the ip without timeout
			iResult = connect( oSkt, (struct sockaddr *)&oSktAddr, iRmtSktAddrLen );
			if( 0 != iResult )
			{
				// connect error
				rc = VxGetLastError();
#ifdef DEBUG_SKTS
				log_msg( LOG_SKT, "VxConnectTo: skt handle %d connect to %s error %s\n",
								oSkt,
								strRmtIp.c_str(),
								DescribeSktErr( rc ) );
#endif // DEBUG_SKTS
				return INVALID_SOCKET;
			}
			else
			{
				bConnectSuccess = true;
				break;
			}
		}
	}
connected:
	if( bConnectSuccess )
	{
		return oSkt;
	}
	else
	{
		return INVALID_SOCKET;
	}
}

//============================================================================
//! set socket to blocking or not
RCODE VxSetSktBlocking(	SOCKET&			oSkt,
						bool			bBlock )
{
	if( bBlock )
	{
		// set to non blocking
#ifdef WIN_PLATFORM
		u_long s32NonBlock = false; 
        int iResult;
		iResult = ioctlsocket( oSkt, FIONBIO, &s32NonBlock );
		if( iResult != 0 )
		{
            RCODE rc = VxGetLastError();
			log_msg( 0, "VxSetSktBlocking ioctlsocket error %s\n", DescribeSktErr( rc ) );
			return rc;
		}
#else
		int iFlags = fcntl(oSkt, F_GETFL, NULL);
		if( 0 >= iFlags )
		{
			log_msg( 0, "VxSetSktBlocking fcntl error\n" );
			return -1;
		}
		iFlags &= (~O_NONBLOCK);
		//log_msg( 0, "VxSetSktBlocking non block flags %d\n", iFlags );
		fcntl( oSkt, F_SETFL, iFlags);

		//fcntl( oSkt, F_SETFL, O_NONBLOCK);
#endif
	}
	else
	{
		// set to non blocking
#ifdef WIN_PLATFORM
		u_long s32NonBlock = true; 
        int iResult = ioctlsocket( oSkt, FIONBIO, &s32NonBlock );
		if( iResult != 0 )
		{
            RCODE rc = VxGetLastError();
			log_msg( 0, "VxSetSktBlocking ioctlsocket error %s\n", DescribeSktErr( rc ) );
			return -1;
		}
#else
		int iFlags = fcntl(oSkt, F_GETFL, NULL);
		if( 0 >= iFlags )
		{
			log_msg( 0, "VxSetSktBlocking fcntl error\n" );
			return -1;
		}
		iFlags |= O_NONBLOCK;
		//log_msg( 0, "VxSetSktBlocking non block flags %d\n", iFlags );
		fcntl( oSkt, F_SETFL, iFlags);
#endif
	}
	return 0;
}

//============================================================================
void VxFlushThenCloseSkt( SOCKET oSocket )
{
#ifdef WIN_PLATFORM
	// set linger time to give socket time to send
	linger oLinger;
	oLinger.l_linger = 20; // how long to linger
	oLinger.l_onoff = 1;
	setsockopt(	oSocket,                 
		(int) IPPROTO_TCP,   //level             
		(int) SO_LINGER,              
		(const char *)&oLinger,  
		(int)sizeof( linger ) );
	// windows crap is broken .. this is the workaround
	// from http://msdn.microsoft.com/en-us/library/ms738547(VS.85).aspx

	//Anyone who wants to do a graceful shutdown (with IOCP in my case) should
	//look at bug 4468997 on the Sun Developers Network.
	//Windows closesocket() is completely broken and actually doesn't work properly.
	//The SO_LINGER and SO_DONTLINGER options also do not work.
	//See:
	//http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4468997
	//What DOES work is it do a
	//DisconnectEx(sock, NULL, 0, 0);
	//closesocket(sock)
	//and this is the only way to get a proper shutdown without clipping trailing
	//bytes from the stream you have just written.
/*
	LPFN_DISCONNECTEX pdisc = NULL; 
	DWORD dwBytesReturned; 
	GUID guidDisconnectEx = WSAID_DISCONNECTEX; 
	WSAIoctl( oSocket, 
		SIO_GET_EXTENSION_FUNCTION_POINTER, 
		&guidDisconnectEx, 
		sizeof(GUID), 
		&pdisc, 
		sizeof(pdisc), 
		&dwBytesReturned, 
		NULL, 
		NULL); 

	if( pdisc )
	{
		//pdisc(oSocket, NULL, TF_REUSE_SOCKET, NULL); 
		pdisc(oSocket, NULL, TF_WRITE_BEHIND, NULL);		
	}
	*/
	shutdown (oSocket, SD_SEND );
#else
	//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d setting linger\n", m_iSktId );
	// set linger time to give socket time to send
	linger oLinger;
	oLinger.l_linger = 1;
	oLinger.l_onoff = 1;
	setsockopt(	oSocket,                 
		(int) IPPROTO_TCP,   //level             
		(int) SO_LINGER,              
		(const char *)&oLinger,  
		(int)sizeof( linger ) );
	//The shutdown command has three options: 0 = done receiving, 1 = done sending, 2 = both
	//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d done setting linger\n", m_iSktId );
	shutdown(oSocket,0);
	//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d shutdown complete\n", m_iSktId );
#endif
}

//============================================================================
void VxCloseSktNow( SOCKET oSocket )
{
	//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d force close start\n", m_iSktId );
	// set linger time to zero to force a close right now
	linger oLinger;
	oLinger.l_linger = 0;
	oLinger.l_onoff = 1;
	setsockopt(	oSocket,                 
		(int) IPPROTO_TCP,   //level             
		(int) SO_LINGER,              
		(const char *)&oLinger,  
		(int)sizeof( linger ) );

	shutdown(oSocket, 0);
#ifdef WIN_PLATFORM
	closesocket( oSocket );
#else // LINUX
	close( oSocket );
#endif //LINUX
	//log_msg( LOG_INFO, "VxSktBase::CloseSkt: Skt %d force close done\n", m_iSktId );
}

//============================================================================
void VxCloseSkt( SOCKET oSocket )
{
#ifdef WIN_PLATFORM
    closesocket( oSocket );
#else // LINUX
    close( oSocket );
#endif //LINUX
}

//============================================================================
bool VxBindSkt( SOCKET oSocket, struct sockaddr_storage * poAddr )
{
    if( SOCKET_ERROR == bind(oSocket, (struct sockaddr *)poAddr, VxGetSktAddressLength(poAddr) ) )
    {
        return false;
    }
    return true;
}

//============================================================================
bool VxBindSkt( SOCKET oSocket, VxInetAddress & oLclAddr, U16 u16Port )
{
	struct sockaddr_storage 		oSktAddr;		// listen socket address
	int sktAddrLen = oLclAddr.fillAddress( oSktAddr, u16Port );
	return VxBindSkt( oSocket, &oSktAddr );
}

//============================================================================
socklen_t VxGetSktAddressLength( struct sockaddr_storage * poAddr )
{
    socklen_t oAddrLen = 0;
    switch( poAddr->ss_family )
    {
    case AF_INET:
        oAddrLen = sizeof(struct sockaddr_in);
        break;

    case AF_INET6:
        oAddrLen = sizeof(struct sockaddr_in6);
        break;

    default:
        log_msg( LOG_FATAL, "VxGetSktAddressLength: Invalid Address Family %d", poAddr->ss_family);
        vx_assert(false);
    }
    return oAddrLen;
}

//============================================================================
void VxSetSktAddressPort( struct sockaddr_storage * poAddr, U16 u16Port )
{
    switch( poAddr->ss_family )
    {
    case AF_INET:
        ((struct sockaddr_in *)poAddr)->sin_port = htons(u16Port);
        break;

    case AF_INET6:
        ((struct sockaddr_in6 *)poAddr)->sin6_port = htons(u16Port);
        break;

    default:
        log_msg( LOG_FATAL, "VxSetSktAddressPort: Invalid Address Family %d", poAddr->ss_family);
        vx_assert(false);
    }
}

//============================================================================
bool VxTestConnectionOnSpecificLclAddress( VxInetAddress& oLclAddr )
{
    bool bConnectionOk = false;
    std::string strLclAddr;
    oLclAddr.toStdString( strLclAddr );
    VxInetAddress oRmtAddr;
    if( VxResolveUrl( "www.todcommunicator.com", 80, oRmtAddr ) )
    {
        // attempt connect
        // Open a socket with the correct address family for this address.
        SOCKET oSktConnect = socket(oRmtAddr.isIPv4() ? PF_INET : PF_INET6, SOCK_STREAM, 0);
        if( oSktConnect == INVALID_SOCKET )
        {
            log_msg( LOG_DEBUG, "VxTestConnectionOnSpecificLclAddress: failed to create socket\n");
        }
        else
        {
            struct sockaddr_storage oLclSktStorage;
            oLclAddr.fillAddress(oLclSktStorage, 0 );
            if( false == VxBindSkt(oSktConnect, &oLclSktStorage ) )
            {
                log_msg( LOG_DEBUG, "VxTestConnectionOnSpecificLclAddress: failed to bind skt with ip %s\n", strLclAddr.c_str() );
            }
            else
            {
                struct sockaddr_storage oRmtSktAddr;
                int iRmtSktAddrLen = oRmtAddr.fillAddress(oRmtSktAddr, 80);

                int result = connect( oSktConnect, (struct sockaddr *)&oRmtSktAddr, iRmtSktAddrLen);
                if( 0 == result )
                {
                    bConnectionOk = true;
                }
                else
                {
                    result = VxGetLastError();
                    log_msg( LOG_ERROR, "VxTestConnectionOnSpecificLclAddress: connect error %s\n", DescribeSktErr( result ) );
                }
            }
            VxCloseSkt( oSktConnect );
        }
    }
    return bConnectionOk;
}

//============================================================================
bool VxResolveUrl( const char * pUrl, U16 u16Port, VxInetAddress& oRetAddr )
{
    bool bResult = false;
    oRetAddr.setToInvalid();

    // get addresses
    struct addrinfo Hints;
    struct addrinfo * AI;
    struct addrinfo * AddrInfo;

    memset(&Hints, 0, sizeof(Hints));
#ifdef WIN_PLATFORM
    Hints.ai_family   = PF_UNSPEC;
    Hints.ai_socktype = SOCK_STREAM;
    Hints.ai_protocol = IPPROTO_TCP;
#else
    Hints.ai_family   = AI_PASSIVE | AI_ADDRCONFIG;
    Hints.ai_socktype = SOCK_STREAM;
    Hints.ai_protocol = IPPROTO_TCP;
#endif
    char as8Buf[32];
    sprintf(as8Buf, "%d", u16Port);


    int RetVal = getaddrinfo(pUrl, as8Buf, &Hints, &AddrInfo);
    if (RetVal != 0)
    {
        //char * pErr = gai_strerror(RetVal);
        //printf("getaddrinfo() failed with error %d: %s\n", RetVal, pErr );
        log_msg( LOG_ERROR, " getaddrinfo() failed with error %d\n", RetVal );
    }
    else
    {

        for(  AI = AddrInfo; AI != NULL; AI = AI->ai_next)
        {
            if ((AI->ai_family != PF_INET) && (AI->ai_family != PF_INET6))
            {
                continue;
            }
            struct sockaddr_storage * poSktAddr = (struct sockaddr_storage *)AI->ai_addr;
            oRetAddr.setIp( *poSktAddr );
            std::string strIp = oRetAddr.toStdString();
            if( oRetAddr.isIPv6())
            {
                continue;
            }
            bResult = true;
            break;
        }
        freeaddrinfo( AddrInfo ); // free the linked list
    }
    return bResult;
}

//============================================================================
//! send data  
RCODE VxSendSktData(	SOCKET&			oSkt,
						const char *	pData,					// data to send
						int				iDataLen,				// length of data
						int				iTimeoutSeconds )		// seconds before send attempt times out
{
	vx_assert( iDataLen > 0 );
	int iSendResult		= 0;
	RCODE m_rcLastError		= 0;
	if( iTimeoutSeconds )
	{
		do
		{
			if( iTimeoutSeconds )
			{
				fd_set	oFdSet;
				FD_ZERO( &oFdSet );
				FD_SET( oSkt, &oFdSet );

				// set timeout val
				struct timeval		oTimeVal;
				oTimeVal.tv_sec		= iTimeoutSeconds;
				oTimeVal.tv_usec	= 0;

				int iSelectResult = select( (int)oSkt + 1, NULL, &oFdSet, NULL, &oTimeVal );
				if( 0 >= iSelectResult )
				{
					//select timed out or select error occurred
					m_rcLastError = VxGetLastError();
					if( 0 == m_rcLastError )
					{
						m_rcLastError = iSelectResult;
					}
					//log_msg( 0, "VxSktCode:SendData: Send to %s Select Error %d\n", GetRemoteIp(), iSelectResult );
					VxCloseSktNow(oSkt);
					return m_rcLastError;
				}
			}
			// send the data
#ifdef WIN_PLATFORM
			iSendResult =  send( oSkt, pData, iDataLen, 0);
#else // LINUX
			// fix so broken pipe does not cause app to exit ( code 141 )
			iSendResult  =  send( oSkt, pData, iDataLen, MSG_NOSIGNAL );
#endif // WIN_PLATFORM
			if( 0 >= iSendResult )
			{
				// send error
				m_rcLastError = VxGetLastError();
				if( 0 == m_rcLastError )
				{
					m_rcLastError = iSendResult;
				}
				VxCloseSktNow(oSkt);
				return m_rcLastError;
			}
			pData += iSendResult;
			iDataLen -= iSendResult;
			if( 0 >= iDataLen )
			{
				// all sent
				break;
			}
			// not all was sent
			VxSleep( 10 ); 
			//if( false == this->IsConnected() )
			//{
			//	log_msg( 0, "VxSktCode::SendData: attempted send on disconnected socket\n" );
			//	return -1;
			//}
		}while( iDataLen > 0 );
	}
	else
	{
		// send the data
#ifdef WIN_PLATFORM
		iSendResult =  send( oSkt, pData, iDataLen, 0);
#else // LINUX
		// fix so broken pipe does not cause app to exit ( code 141 )
		iSendResult  =  send( oSkt, pData, iDataLen, MSG_NOSIGNAL );
#endif // WIN_PLATFORM
		if( 0 >= iSendResult )
		{
			// send error
			m_rcLastError = VxGetLastError();
			if( 0 == m_rcLastError )
			{
				m_rcLastError = iSendResult;
			}
			VxCloseSktNow(oSkt);
			return m_rcLastError;
		}
		return 0;
	}
	return 0;

}

//============================================================================
//! receive data.. if timeout is set then will keep trying till buffer is full or error or timeout expires
RCODE VxRecieveSktData(	SOCKET&			oSkt,
						char *			pRetBuf,				// buffer to receive data into
						int				iBufLenIn,				// length of buffer
						int *			iRetBytesRecieved,		// number of bytes actually received
						int				iTimeoutMilliSeconds,	// milliseconds before receive attempt times out ( 0 = dont wait )
						BOOL			bAbortIfCrLfCrLf,		// if true then abort receive when \r\n\r\n is received
						BOOL *			pbRetGotCrLfCrLf )		// if received \r\n\r\n set to true
{
	RCODE m_rcLastError = 0; // clear out any previous error
	* iRetBytesRecieved = 0;
	if( pbRetGotCrLfCrLf )
	{
		* pbRetGotCrLfCrLf = 0;
	}
	int iBufLen = iBufLenIn - 8; // reserve a little space for /r/n etc

	int	iRecvResult;
	vx_assert( pRetBuf );
	vx_assert( iBufLen > 0 );
	vx_assert( iRetBytesRecieved );
	VxSetSktBlocking(  oSkt, false );

	VxTimer		oTimer;
	do
	{
		iRecvResult = recv(		oSkt,		// socket
			pRetBuf,		// buffer to read into
			iBufLen,		// length of buffer space
			0 );			// flags
		if( 0 > iRecvResult )
		{
			iRecvResult = VxGetLastError();
			//log_msg( LOG_INFO, "VxSktCode::RecieveData: error %d timeout %d\n", iDataLen, iTimeoutMilliSeconds );
			if( (11 == iRecvResult) || (EINPROGRESS == iRecvResult) || iTimeoutMilliSeconds )
			{
				// see if have timeout value
				if( iTimeoutMilliSeconds )
				{
					if( iTimeoutMilliSeconds < oTimer.ElapsedMilliSec() )
					{
						// timeout
						break;
					}
					else
					{
						VxSleep( 10 );
						continue;
					}
				}
				else
				{
					// no timeout.. if any data already received then bail with the data received
					if( * iRetBytesRecieved > 0 )
					{
						break;
					}
				}
				VxSleep( 20 );
				continue;
			}
			// not in progress so is a bad error
			m_rcLastError = iRecvResult;
			return m_rcLastError;

		}
		if( INVALID_SOCKET == oSkt  )
		{
			m_rcLastError = VxGetLastError();
			log_msg( 0, "VxSktCode:recv error %d %s\n", m_rcLastError, DescribeSktErr( m_rcLastError ) );
			VxCloseSktNow( oSkt );
			return m_rcLastError; //exit
		}
		if( 0 == iRecvResult )
		{
			// may not of had time to get some data
			// see if have timeout value
			if( iTimeoutMilliSeconds )
			{
				if( iTimeoutMilliSeconds < oTimer.ElapsedMilliSec() )
				{
					// timeout
					break;
				}
				else
				{
					VxSleep( 10 );
					continue;
				}
			}
		}
		// got some data
		* iRetBytesRecieved += iRecvResult;
		iBufLen -= iRecvResult;
		// null terminate the string
		pRetBuf[ iRecvResult ] = 0;
		if( bAbortIfCrLfCrLf )								// if abort when \r\n\r\n is received 
		{

			if( strstr( pRetBuf, "\r\n\r\n" ) )
			{
				// all done
				if( pbRetGotCrLfCrLf )
				{
					*pbRetGotCrLfCrLf = true;
				}
			}
		}
		// advance to end of received data for next receive
		pRetBuf += iRecvResult;
		if( ( 0 >= iBufLen) || 
			( pbRetGotCrLfCrLf && *pbRetGotCrLfCrLf ) )
		{
			// all full or got a CrLfCrLf
			break;
		}
		if( iTimeoutMilliSeconds )
		{
			if( iTimeoutMilliSeconds < oTimer.ElapsedMilliSec() )
			{
				// timeout
				break;
			}
			else
			{
				continue;
			}
		}
		// reset timeout
		oTimer.StartTimer();
	}while( iRecvResult > 0 );
	return m_rcLastError; 
}

//============================================================================
bool VxIsIPv6Address( const char *addr )
{
	if( NULL == addr )
	{
		return false;
	}
	std::string addrStr = addr;
	if( addrStr.find(":") != std::string::npos )
	{
		return true;
	}
	return false;
}

//============================================================================
int VxGetIPv6ScopeID(const char *addr)
{
	if( false == VxIsIPv6Address( addr ) )
	{
		return 0;
	}
	std::string addrStr = addr;
	int pos = (int)addrStr.find("%");
	if (pos == (int)std::string::npos)
	{
		return 0;
	}
	std::string scopeStr = addrStr.substr(pos+1, addrStr.length());
	return atoi(scopeStr.c_str());
}

//============================================================================
const char * VxStripIPv6ScopeID( const char *addr, std::string &buf )
{
	std::string addrStr = addr;
	if( true == VxIsIPv6Address( addr ) ) 
	{
		unsigned int pos = (int)addrStr.find("%");
		if (pos != std::string::npos)
			addrStr = addrStr.substr(0, pos);
	}
	buf = addrStr;
	return buf.c_str();
}
